goog.provide('mp.tools.Matrix');
//goog.exportSymbol('mp.tools.Matrix', mp.tools.Matrix);

goog.require('goog.debug.Logger');
goog.require('goog.math.Coordinate');
goog.require('goog.math.Matrix');
goog.require('goog.structs.Map');



/**
 * @param {goog.math.Matrix|Array.<Array.<number>>|goog.math.Size|number} m
 *     A matrix to copy, a 2D-array to take as a template, a size object for
 *     dimensions, or the number of rows.
 * @param {number=} opt_n Number of columns of the matrix (only applicable if
 *     the first argument is also numeric).
 * @constructor
 * @extends {goog.math.Matrix}
 */
mp.tools.Matrix = function(m, opt_n) {
  goog.base(this, m, opt_n);
};
goog.inherits(mp.tools.Matrix, goog.math.Matrix);


/**
 * @const
 * @type {number}
 */
mp.tools.Matrix.EMPTY = 0;


/**
 * Fix none matrix array.
 * @param {Array} array2D An array to work.
 */
mp.tools.Matrix.makeSquare = function(array2D) {
  // process matrix
  var _max = 0;
  for (var r = array2D.length - 1; r >= 0; r--) {
    _max = Math.max(_max, array2D[r].length);
  }

  for (var r = array2D.length - 1; r >= 0; r--) {
    // Fill empty spaces. Make matrix rectangular.
    var _dif = _max - array2D[r].length;

    if (_dif > 0)
      mp.tools.Matrix.prototype.logger.info(arguments.callee.name +
          ' :: Expanding row ' + r);

    for (var s = 0; s < _dif; s++)
      array2D[r].push(mp.tools.Matrix.EMPTY);
  }
};


/**
 * @type {goog.debug.Logger}
 * @protected
 */
mp.tools.Matrix.prototype.logger =
    goog.debug.Logger.getLogger('mp.tools.Matrix');


/**
 * @return {mp.tools.Matrix} New Matrix.
 */
mp.tools.Matrix.prototype.clone = function() {
  //return new mp.tools.Matrix(JSON.parse(JSON.stringify(this.toArray())));
  return new mp.tools.Matrix(goog.object.unsafeClone(
      /** @type {Array.<Array.<number>>} */ (this.toArray())));
};


/**
 * @return {number} Width.
 */
mp.tools.Matrix.prototype.width = function() {
  return this.getSize().width;
};


/**
 * @return {number} Height.
 */
mp.tools.Matrix.prototype.height = function() {
  return this.getSize().height;
};


/**
 * Return line.
 * @param {number} idx Line idx.
 * @param {boolean} isVLine True if process vertical line.
 * @return {Array.<number>} 2D array line.
 */
mp.tools.Matrix.prototype.line = function(idx, isVLine) {
  if (isVLine) {
    var _line = [],
        _h = this.height();
    for (var r = 0; r < _h; r++)
      _line.push(this.array_[r][idx]);
    return _line;
  } else {
    return goog.object.unsafeClone(this.array_[idx]);
  }
};


/**
 * Insert new line.
 * @param {number} idx Line idx.
 * @param {Array.<number>} line 2D array line.
 * @param {boolean} isVLine True if process vertical line.
 * @return {mp.tools.Matrix} New Matrix.
 */
mp.tools.Matrix.prototype.insertLine = function(idx, line, isVLine) {
  var _array2D = /** @type {Array.<Array.<number>>} */
      (goog.object.unsafeClone(this.array_));

  if (isVLine) {
    var _h = this.height();
    if (line.length != _h)
      throw Error('mp.tools.Matrix:', 'Not valid line!');
    for (var r = 0; r < _h; r++) {
      _array2D[r].splice(idx, 0, line[r]);
    }
  } else {
    if (line.length != this.width())
      throw Error('mp.tools.Matrix:', 'Not valid line!');
    _array2D.splice(idx, 0, line);
  }

  return new mp.tools.Matrix(_array2D);
};


/**
 * Remove line.
 * @param {number} idx Line idx.
 * @param {boolean} isVLine True if process vertical line.
 * @return {mp.tools.Matrix} New Matrix.
 */
mp.tools.Matrix.prototype.removeLine = function(idx, isVLine) {
  var _array2D = /** @type {Array.<Array.<number>>} */
      (goog.object.unsafeClone(this.array_));

  if (isVLine) {
    for (var r = this.height() - 1; r >= 0; r--) {
      _array2D[r].splice(idx, 1);
    }
  } else {
    _array2D.splice(idx, 1);
  }
  return new mp.tools.Matrix(_array2D);
};


/**
 * @return {mp.tools.Matrix} New Matrix.
 */
mp.tools.Matrix.prototype.rotate = function() {
  var _array2D = [], _w = this.width();
  for (var c = 0; c < _w; c++) {
    var _line = this.line(c, true);
    _line.reverse();
    _array2D.push(_line);
  }
  return new mp.tools.Matrix(_array2D);
};


/**
 * Reflect non empty cell values.
 * @param {number} nonEmpty Non empty number.
 */
mp.tools.Matrix.prototype.reflectNonEmpty = function(nonEmpty) {
  var _h = this.height(),
      _w = this.width();
  for (var r = _h - 1; r >= 0; r--) {
    if (this.array_[r][0] == nonEmpty &&
        this.array_[r][_w - 1] == mp.tools.Matrix.EMPTY)
      this.array_[r][_w - 1] = nonEmpty;
  }
  for (var c = _w - 1; c >= 0; c--) {
    if (this.array_[0][c] == nonEmpty &&
        this.array_[_h - 1][c] == mp.tools.Matrix.EMPTY)
      this.array_[_h - 1][c] = nonEmpty;
  }
};


/**
 * Get non empty values from the edge.
 * @param {number} nonEmpty Non empty number.
 * @param {boolean} isVert True if process vertical edge.
 * @return {Array.<number>} Indexes.
 */
mp.tools.Matrix.prototype.getNonEmptyEdgeIdxs = function(nonEmpty, isVert) {
  var _idxs = [],
      _line = this.line(0, isVert),
      _len = _line.length;
  for (var i = 0; i < _len; i++) {
    if (_line[i] == nonEmpty)
      _idxs.push(i);
  }
  return _idxs;
};


/**
 * Get object map where 'key' is value in matrix and 'value' is point array.
 * @return {goog.structs.Map} Map of {Array.<goog.math.Coordinate>}.
 */
mp.tools.Matrix.prototype.getValueIdxsMap = function() {
  var _map = new goog.structs.Map(),
      h = this.height(),
      w = this.width();

  for (var r = 0; r < h; r++) {
    for (var c = 0; c < w; c++) {
      var key = this.array_[r][c];
      if (!_map.containsKey(key))
        _map.set(key, []);
      _map.get(key).push(new goog.math.Coordinate(c, r));
    }
  }
  return _map;
};


/**
 * Get neighbours around cell item.
 * @param {number} r Row index.
 * @param {number} c Column index.
 * @param {boolean=} opt_wDiagonal With diagonal elements.
 * @return {Array.<number>} List of cell values.
 */
mp.tools.Matrix.prototype.getNeighbours = function(r, c, opt_wDiagonal) {
  var _values = [],
      _cells = [
        new goog.math.Coordinate(c, r - 1),
        new goog.math.Coordinate(c, r + 1),
        new goog.math.Coordinate(c - 1, r),
        new goog.math.Coordinate(c + 1, r)
      ];

  if (!! opt_wDiagonal) {
    _cells.push(new goog.math.Coordinate(c - 1, r - 1));
    _cells.push(new goog.math.Coordinate(c - 1, r + 1));
    _cells.push(new goog.math.Coordinate(c + 1, r - 1));
    _cells.push(new goog.math.Coordinate(c + 1, r + 1));
  }

  for (var i = _cells.length - 1; i >= 0; i--) {
    var _val = this.getValueAt(_cells[i].y, _cells[i].x);
    if (_val !== null)
      _values.push(_val);
  }

  return _values;
};

